using System;
using System.Text;

using allegro;

namespace exlights
{
    // TODO: issues with color conversion/lookup tables, check why colors are messed up
    class exlights : Allegro
    {
        /* double buffer bitmap */
        static BITMAP buffer;


        /* the two moving bitmap images */
        static BITMAP image1;
        static BITMAP image2;


        /* the light map, drawn wherever the mouse pointer is */
        static BITMAP lightmap;


        /* give images some light of their own. Make this zero for total black */
        const int AMBIENT_LIGHT = 0x20;



        /* converts a bitmap from the normal Allegro format into our magic layout */
        static BITMAP get_magic_bitmap_format(BITMAP orig, PALETTE pal)
        {
            BITMAP bmp;
            int c, r, g, b;
            int x, y;
            int bpp;

            /* create an 8 bpp image, three times as wide as the original */
            bmp = create_bitmap_ex(8, orig.w * 3, orig.h);

            /* store info about the original bitmap format */
            bpp = bitmap_color_depth(orig);
            //select_palette(pal);

            /* convert the data */
            for (y = 0; y < orig.h; y++)
            {
                for (x = 0; x < orig.w; x++)
                {
                    c = getpixel(orig, x, y);

                    r = getr_depth(bpp, c);
                    g = getg_depth(bpp, c);
                    b = getb_depth(bpp, c);

                    //bmp->line[y][x*3] = r*31/255 | AMBIENT_LIGHT;
                    //bmp->line[y][x*3+1] = g*31/255 | AMBIENT_LIGHT;
                    //bmp->line[y][x*3+2] = b*31/255 | AMBIENT_LIGHT;

                    r = (r * 31 / 255) | AMBIENT_LIGHT;
                    g = (g * 31 / 255) | AMBIENT_LIGHT;
                    b = (b * 31 / 255) | AMBIENT_LIGHT;

                    ManagedBytePointerArray line = new ManagedBytePointerArray(bmp.line).Offset(y * 3 * orig.w * sizeof(byte));
                    line[x * 3] = (byte)r;
                    line[x * 3 + 1] = (byte)g;
                    line[x * 3 + 2] = (byte)b;
                }
            }

            /* return our new, magic format version of the image */
            return bmp;
        }

        static void magic_putpix(BITMAP bmp, int x, int y, int r, int g, int b)
        {
            //bmp.line[y][(x) * 3] &= 0xE0;
            //bmp.line[y][(x) * 3 + 1] &= 0xE0;
            //bmp.line[y][(x) * 3 + 2] &= 0xE0;
            ManagedBytePointerArray line = new ManagedBytePointerArray(bmp.line).Offset(y * 3 * 256 * sizeof(byte));
            line[x * 3] &= 0xE0;
            line[x * 3 + 1] &= 0xE0;
            line[x * 3 + 2] &= 0xE0;

            //bmp.line[y][(x) * 3] |= r;
            //bmp.line[y][(x) * 3 + 1] |= g;
            //bmp.line[y][(x) * 3 + 2] |= b;      
            line[x * 3] |= (byte)r;
            line[x * 3 + 1] |= (byte)g;
            line[x * 3 + 2] |= (byte)b;
        }

        /* creates the light graphic for the mouse pointer, in our magic format */
        static BITMAP create_light_graphic()
        {
            BITMAP bmp;
            int x, y;
            int dx, dy;
            int dist, dir;
            int r, g, b;

            bmp = create_bitmap_ex(8, 256 * 3, 256);

            /* draw the light (a circular color gradient) */
            for (y = 0; y < 256; y++)
            {
                for (x = 0; x < 256; x++)
                {
                    dx = x - 128;
                    dy = y - 128;

                    dist = fixtoi(fixsqrt(itofix(MID(-32768, dx * dx + dy * dy, 32767))));

                    // TODO: check why it corrupts memory
                    dir = 1; // fixtoi(fixatan2(itofix(dy), itofix(dx)) + itofix(128));

                    hsv_to_rgb(dir * 360.0f / 256.0f, MID(0, (int)(dist / 128.0), 1), 1f, out r, out g, out b);

                    r = r * (128 - dist) / 1024;
                    g = g * (128 - dist) / 1024;
                    b = b * (128 - dist) / 1024;

                    //bmp.line[y][x * 3] = MID(0, r, 7) << 5;
                    //bmp.line[y][x * 3 + 1] = MID(0, g, 7) << 5;
                    //bmp.line[y][x * 3 + 2] = MID(0, b, 7) << 5;

                    ManagedBytePointerArray line = new ManagedBytePointerArray(bmp.line).Offset(y * 3 * 256 * sizeof(byte));
                    line[x * 3] = (byte)(MID(0, r, 7) << 5);
                    line[x * 3 + 1] = (byte)(MID(0, g, 7) << 5);
                    line[x * 3 + 2] = (byte)(MID(0, b, 7) << 5);
                }
            }

            /* draw some solid pixels as well (a cross in the middle ) */
            for (x = -15; x <= 15; x++)
            {
                for (y = -2; y <= 2; y++)
                {
                    magic_putpix(bmp, 128 + x, 128 + y, x + 15, 15 - x, 0);
                    magic_putpix(bmp, 128 + y, 128 + x, x + 15, x + 15, 15 - x);
                }
            }

            return bmp;
        }



#if ALLEGRO_LITTLE_ENDIAN

        /* lookup tables for speeding up the color conversion */
        static ushort[] rgtable = new ushort[65536];
        static uint[] brtable = new uint[65536];
        static ushort[] gbtable = new ushort[65536];



        /* builds some helper tables for doing color conversions */
        static void generate_conversion_tables()
        {
            int r, g, b;
            int cr, cg, cb;

            /* this table combines a 16 bit r+g value into a screen pixel */
            for (r = 0; r < 256; r++)
            {
                cr = (r & 31) * (r >> 5) * 255 / 217;
                for (g = 0; g < 256; g++)
                {
                    cg = (g & 31) * (g >> 5) * 255 / 217;
                    rgtable[r + g * 256] = (ushort)makecol(cr, cg, 0);
                }
            }

            /* this table combines a 16 bit b+r value into a screen pixel */
            for (b = 0; b < 256; b++)
            {
                cb = (b & 31) * (b >> 5) * 255 / 217;
                for (r = 0; r < 256; r++)
                {
                    cr = (r & 31) * (r >> 5) * 255 / 217;
                    brtable[b + r * 256] = (uint)(makecol(0, 0, cb) | (makecol(cr, 0, 0) << 16));
                }
            }

            /* this table combines a 16 bit g+b value into a screen pixel */
            for (g = 0; g < 256; g++)
            {
                cg = (g & 31) * (g >> 5) * 255 / 217;
                for (b = 0; b < 256; b++)
                {
                    cb = (b & 31) * (b >> 5) * 255 / 217;
                    gbtable[g + b * 256] = (ushort)makecol(0, cg, cb);
                }
            }
        }



        /* copies from our magic format data onto a normal Allegro screen bitmap */
        static void blit_magic_format_to_screen(BITMAP bmp)
        {
            uint addr;
            ManagedPointer data;
            uint in1, in2, in3;
            uint out1, out2;
            int x, y;

            /* Warning: this function contains some rather hairy optimisations :-)
             * The fastest way to copy large amounts of data is in aligned 32 bit 
             * chunks. If we expand it out to process 4 pixels per cycle, we can 
             * do this by reading 12 bytes (4 pixels) from the 24 bit source data, 
             * and writing 8 bytes (4 pixels) to the 16 bit destination. Then the 
             * only problem is how to convert our 12 bytes of data into a suitable 
             * 8 byte format, once we've got it loaded into registers. This is done 
             * by some lookup tables, which are arranged so they can process 2 color 
             * components in a single lookup, and allow me to precalculate the 
             * makecol() operation.
             *
             * Warning #2: this code assumes little-endianess.
             *
             * Here is a (rather confusing) attempt to diagram the logic of the
             * lookup table lighting conversion from 24 to 16 bit format in little-
             * endian format:
             *
             *
             *  inputs: |     (dword 1)     |     (dword 2)     |     (dword 3)     |
             *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
             *  bytes:  | r1   g1   b1   r2   g2   b2   r3   g3   b3   r4   g4   b4 |
             *          |    |         |         |         |         |         |    |
             *  lookup: | rgtable   brtable   gbtable   rgtable   brtable   gbtable |
             *          |    |         |         |         |         |         |    |
             *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
             *  outputs |          (dword 1)          |          (dword 2)          |
             *
             *
             * For reference, here is the original, non-optimised but much easier
             * to understand version of this routine:
             *
             *    _farsetsel(screen->seg);
             * 
             *    for (y=0; y<SCREEN_H; y++) {
             *       addr = bmp_write_line(screen, y);
             * 
             *       for (x=0; x<SCREEN_W; x++) {
             *          r = bmp->line[y][x*3];
             *          g = bmp->line[y][x*3+1];
             *          b = bmp->line[y][x*3+2];
             * 
             *          r = (r&31) * (r>>5) * 255/217;
             *          g = (g&31) * (g>>5) * 255/217;
             *          b = (b&31) * (b>>5) * 255/217;
             * 
             *          _farnspokew(addr, makecol(r, g, b));
             * 
             *          addr += 2;
             *       }
             *    }
             */

            bmp_select(screen);

            for (y = 0; y < SCREEN_H; y++)
            {
                addr = bmp_write_line(screen, y);
                //data = (uint32_t*)bmp->line[y];
                data = new ManagedPointer(bmp.line).Offset(y * bmp.w * sizeof(byte));

                for (x = 0; x < SCREEN_W / 4; x++)
                {
                    //in1 = *(data++);
                    //in2 = *(data++);
                    //in3 = *(data++);
                    in1 = (uint)data.ReadInt(0);
                    in2 = (uint)data.ReadInt(sizeof(Int32));
                    in3 = (uint)data.ReadInt(2 * sizeof(Int32));
                    data = data.Offset(3 * sizeof(Int32));

                    /* trust me, this does make sense, really :-) */
                    out1 = (uint)rgtable[in1 & 0xFFFF] |
                       (uint)brtable[in1 >> 16] |
                       (uint)(gbtable[in2 & 0xFFFF] << 16);

                    out2 = (uint)rgtable[in2 >> 16] |
                       (uint)brtable[in3 & 0xFFFF] |
                       (uint)(gbtable[in3 >> 16] << 16);

                    bmp_write32(addr, (int)out1);
                    bmp_write32(addr + 4, (int)out2);

                    addr += 8;
                }
            }

            bmp_unwrite_line(screen);
        }


#elif ALLEGRO_BIG_ENDIAN


/* lookup tables for speeding up the color conversion */
unsigned short bgtable[65536];
unsigned long rbtable[65536];
unsigned short grtable[65536];



/* builds some helper tables for doing color conversions */
void generate_conversion_tables(void)
{
   int r, g, b;
   int cr, cg, cb;

   /* this table combines a 16 bit b+g value into a screen pixel */
   for (b=0; b<256; b++) {
      cb = (b&31) * (b>>5) * 255/217;
      for (g=0; g<256; g++) {
	 cg = (g&31) * (g>>5) * 255/217;
	 bgtable[b+g*256] = makecol(0, cg, cb);
      }
   }

   /* this table combines a 16 bit g+r value into a screen pixel */
   for (r=0; r<256; r++) {
      cr = (r&31) * (r>>5) * 255/217;
      for (b=0; b<256; b++) {
	 cb = (b&31) * (b>>5) * 255/217;
	 rbtable[r+b*256] = makecol(cr, 0, 0) | (makecol(0, 0, cb) << 16);
      }
   }

   /* this table combines a 16 bit r+r value into a screen pixel */
   for (g=0; g<256; g++) {
      cg = (g&31) * (g>>5) * 255/217;
      for (r=0; r<256; r++) {
	 cr = (r&31) * (r>>5) * 255/217;
	 grtable[g+r*256] = makecol(cr, cg, 0);
      }
   }
}



/* copies from our magic format data onto a normal Allegro screen bitmap */
void blit_magic_format_to_screen(BITMAP *bmp)
{
   uintptr_t addr;
   uint32_t *data;
   unsigned long in1, in2, in3, temp1, temp2, temp3;
   unsigned long out1, out2;
   int x, y;

   /* Warning: this is the big-endian version of the routine above. We need
    * a different lookup tables arrangement and we also need to shuffle the
    * bytes order before doing the lookup.
    *
    * Here is a (rather confusing) attempt to diagram the logic of the
    * lookup table lighting conversion from 24 to 16 bit format in big-
    * endian format:
    *
    *
    *  inputs: |     (dword 1)     |     (dword 2)     |     (dword 3)     |
    *  pixels: |   (pixel1)   |   (pixel2)   |   (pixel3)   |   (pixel4)   |
    *  bytes:  | r2   b1   g1   r1   g3   r3   b2   g2   b4   g4   r4   b3 |
    *  bytes2: | b2   g2   r2   b1   g1   r1   b4   g4   r4   b3   g3   r3 |
    *          |    |         |         |         |         |         |    |
    *  lookup: | bgtable   rbtable   grtable   bgtable   rbtable   grtable |
    *          |    |         |         |         |         |         |    |
    *  pixels: |   (pixel2)   |   (pixel1)   |   (pixel4)   |   (pixel3)   |
    *  outputs |          (dword 1)          |          (dword 2)          |
    */

   bmp_select(screen);

   for (y=0; y<SCREEN_H; y++) {
      addr = bmp_write_line(screen, y);
      data = (uint32_t *)bmp->line[y];

      for (x=0; x<SCREEN_W/4; x++) {
	 in1 = *(data++);
	 in2 = *(data++);
	 in3 = *(data++);

	 /* trust me, this does make sense, really :-) */
	 temp1 = (in1 << 16) | (in2 >> 16);
	 temp2 = (in1 >> 16) | (in3 << 16);
	 temp3 = (in3 >> 16) | (in2 << 16);

	 out1 = bgtable[temp1&0xFFFF] |
	        rbtable[temp1>>16] |
		(grtable[temp2&0xFFFF] << 16);

	 out2 = bgtable[temp2>>16] |
	        rbtable[temp3&0xFFFF] |
		(grtable[temp3>>16] << 16);

	 bmp_write32(addr, out1);
	 bmp_write32(addr+4, out2);

	 addr += 8;
      }
   }

   bmp_unwrite_line(screen);
}


#elif !SCAN_DEPEND
#error Unknown endianess!
#endif



        static int Main(string[] argv)
        {
            BITMAP tmp;
            PALETTE pal = new PALETTE();
            //char buf[256];
            byte[] buf = new byte[256];
            int x, y, xc, yc, xl, yl, c, l;

            if (allegro_init() != 0)
                return 1;
            install_keyboard();
            install_timer();
            install_mouse();
            set_color_conversion(COLORCONV_NONE);

            /* set a 15 or 16 bpp video mode */
            set_color_depth(16);
            if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0)
            {
                set_color_depth(15);
                if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0)
                {
                    set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
                    allegro_message(string.Format("Error setting a 15 or 16 bpp 640x480 video mode\n{0}\n", allegro_error));
                    return 1;
                }
            }

            /* create the double buffer, 8 bpp and three times as wide as the screen */
            buffer = create_bitmap_ex(8, SCREEN_W * 3, SCREEN_H);

            /* load the first picture */
            //replace_filename(buf, argv[0], "allegro.pcx", sizeof(buf));
            //tmp = load_bitmap(buf, pal);
            tmp = load_bitmap("allegro.pcx", pal);
            if (!tmp)
            {
                destroy_bitmap(buffer);
                set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
                allegro_message(string.Format("Error reading {0}!\n", buf));
                return 1;
            }

            /* convert it into our special format */
            image1 = get_magic_bitmap_format(tmp, pal);
            destroy_bitmap(tmp);

            /* load the second picture */
            //replace_filename(buf, argv[0], "mysha.pcx", sizeof(buf));
            //tmp = load_bitmap(buf, pal);
            tmp = load_bitmap("mysha.pcx", pal);
            if (!tmp)
            {
                destroy_bitmap(buffer);
                destroy_bitmap(image1);
                set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
                allegro_message(string.Format("Error reading {0}!\n", buf));
                return 1;
            }

            ///* convert it into our special format */
            image2 = get_magic_bitmap_format(tmp, pal);
            destroy_bitmap(tmp);

            ///* create the light map image */
            lightmap = create_light_graphic();

            /* create our custom color blending map, which does translucency in the
             * bottom five bits and adds the light levels in the top three bits.
             * This version just does a 50% translucency if you are drawing sprites
             * with it, but you could easily make other color maps for different 
             * alpha levels, or for doing additive color, which can work happily
             * in parallel with the light blending.
             */
            //color_map = malloc(sizeof(COLOR_MAP));
            COLOR_MAP _color_map = new COLOR_MAP();

            for (x = 0; x < 256; x++)
            {
                for (y = 0; y < 256; y++)
                {
                    xc = x & 31;
                    yc = y & 31;

                    xl = x >> 5;
                    yl = y >> 5;

                    if (xc > 0)
                        c = (xc + yc) / 2;
                    else
                        c = yc;

                    l = xl + yl;
                    if (l > 7)
                        l = 7;

                    _color_map.data[x, y] = (byte)(c | (l << 5));
                }
            }
            color_map = _color_map;

            /* generate tables for converting pixels from magic->screen format */
            generate_conversion_tables();

            /* display the animation */
            while (!keypressed())
            {
                poll_mouse();

                clear_bitmap(buffer);

                /* we can draw the graphics using normal calls, just as if they were
                 * regular 256 color images. Everything is just three times as wide 
                 * as it would usually be, so we need to make sure that we only ever
                 * draw to an x coordinate that is a multiple of three (otherwise
                 * all the colors would get shifted out of phase with each other).
                 */
                blit(image1, buffer, 0, 0, 0, retrace_count % (SCREEN_H + image1.h) - image1.h, image1.w, image1.h);
                blit(image2, buffer, 0, 0, buffer.w - image2.w, buffer.h - (retrace_count * 2 / 3) % (SCREEN_H + image2.h), image2.w, image2.h);
                

                /* now we overlay translucent graphics and lights. Having set up a
                 * suitable color blending table, these can be done at the same time,
                 * either drawing a series of images some of which are translucent
                 * sprites and some of which are light maps, or if we want, we can
                 * just draw a single bitmap containing both color and light data 
                 * with a single call, like this! You could also use graphics 
                 * primitives like circlefill() to draw the lights, as long as you
                 * do it in a translucent mode.
                 */
                draw_trans_sprite(buffer, lightmap, (mouse_x - lightmap.w / 6) * 3, mouse_y - lightmap.h / 2);

                /* this function is the key to the whole thing, converting from our
                 * weird 5+3 interleaved format into a regular hicolor pixel that
                 * can be displayed on your monitor.
                 */
                blit_magic_format_to_screen(buffer);
            }

            clear_keybuf();

            destroy_bitmap(buffer);
            destroy_bitmap(image1);
            destroy_bitmap(image2);
            destroy_bitmap(lightmap);

            //free(color_map);

            return 0;
        }
    }
}
